// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_entity_water.h"
#include "mk_error.h"
#include "mk_engine.h"
#include "mk_math.h"
#include "mk_shader.h"
#include "mk_render_input.h"
#include "mk_hardware_buffer.h"
#include "mk_texture.h"
#include "mk_render_device.h"
#include "mk_resource_manager.h"
#include "mk_ref_counter.h"
#include "mk_water.h"
#include "mk_scene_node.h"
#include "mk_camera.h"

namespace MK3DE
{
	CEntityWater::CEntityWater(CScene* pScene)
		: CEntity(pScene)
	{
		_coreWater = NULL;
		_vbPosition = NULL;
		_ibIndex = NULL;
		_renderInput = NULL;
		_shader = NULL;
		_bumpTexture = NULL;
		_reflectionTexture = NULL;
		_refractionTexture = NULL;
	}

	CEntityWater::~CEntityWater()
	{
		SAFE_DELETE(_coreWater);
		CRenderDevice* rd = GetEngine()->GetRenderDevice();
		SAFE_RELEASE(_vbPosition);
		SAFE_RELEASE(_ibIndex);
		rd->DestroyRenderInput(_renderInput);
		SAFE_RELEASE(_bumpTexture);
		SAFE_RELEASE(_reflectionTexture);
		SAFE_RELEASE(_refractionTexture);
	}

	BOOL CEntityWater::Create(DWORD dwWidth, DWORD dwHeight, float fGridSize, const char* shaderFileName)
	{
		BOOL ret = FALSE;

		_coreWater = new CWater();
		MK_PROCESS_ERROR(_coreWater);

		ret = _coreWater->CreateMesh(dwWidth, dwHeight, fGridSize);
		MK_PROCESS_ERROR(ret);

		CRenderDevice* rd = GetEngine()->GetRenderDevice();
		CResourceManager* rm = GetEngine()->GetResourceManager();

		DWORD vertexCount = (dwWidth + 1) * (dwHeight + 1);
		DWORD faceCount = dwWidth * dwHeight * 2;

		// create vertex buffer and index buffer.
		ret = rm->GetVertexBuffer(sizeof(D3DXVECTOR3), vertexCount, HBU_STATIC, _vbPosition);
		MK_PROCESS_ERROR(ret);

		ret = rm->GetIndexBuffer(faceCount * 3, IT_32BIT, HBU_STATIC, _ibIndex);
		MK_PROCESS_ERROR(ret);

		// fill position vertex buffer.
		CMesh::Position* pos;
		ret = _vbPosition->Lock((void*&)pos, 0, 0, LO_READ_WRITE);
		MK_PROCESS_ERROR(ret);

		memcpy(pos, &_coreWater->_waterData.vecPosition[0], sizeof(CMesh::Position) * vertexCount);

		ret = _vbPosition->Unlock();
		MK_PROCESS_ERROR(ret);

		// fill face index buffer.
		DWORD* pi;
		ret = _ibIndex->Lock((void*&)pi, 0, 0, LO_READ_WRITE);
		MK_PROCESS_ERROR(ret);

		for (DWORD y = 0; y < dwHeight; ++y)
		{
			for (DWORD x = 0; x < dwWidth; ++x)
			{
				*pi++ = y * (dwWidth + 1) + x;
				*pi++ = (y + 1) * (dwWidth + 1) + x;
				*pi++ = y * (dwWidth + 1) + x + 1;

				*pi++ = (y + 1) * (dwWidth + 1) + x;
				*pi++ = (y + 1) * (dwWidth + 1) + x + 1;
				*pi++ = y * (dwWidth + 1) + x + 1;
			}
		}

		ret = _ibIndex->Unlock();
		MK_PROCESS_ERROR(ret);

		RenderInputElemDesc rie[] = { 
			RenderInputElemDesc(SU_POSITION, 0, 0, 0),
		};

		// create render input for terrain
		ret = rd->CreateRenderInput(rie, 1, _renderInput);
		MK_PROCESS_ERROR(ret);

		ret = _renderInput->SetVertexBuffer(0, _vbPosition);
		MK_PROCESS_ERROR(ret);

		_renderInput->SetIndexBuffer(_ibIndex);

		ret = GetEngine()->GetResourceManager()->GetShaderFromFile(shaderFileName, _shader);
		MK_PROCESS_ERROR(ret);

		UpdateLocalAABB();

		ret = TRUE;
Exit0:
		return ret;
	}

	void CEntityWater::UpdateLocalAABB()
	{
		float maxX = (float)_coreWater->_width * _coreWater->_gridSize;
		float maxY = (float)_coreWater->_height * _coreWater->_gridSize;
		_localAABB.Merge(&D3DXVECTOR3(0, -10.0f, 0));
		_localAABB.Merge(&D3DXVECTOR3(maxX, 10.0f, maxY));
		UpdateWorldAABB();
	}

	BOOL CEntityWater::CreateBumpTexture(const char* fileName)
	{
		BOOL ret = GetEngine()->GetResourceManager()->GetTexture2DFromFile(fileName, _bumpTexture);
		MK_PROCESS_ERROR(ret);

		return TRUE;
Exit0:
		return FALSE;
	}

	void CEntityWater::SetReflectionTexture(CTexture2D* pTexture)
	{
		SAFE_RELEASE(_reflectionTexture);
		_reflectionTexture = pTexture;
		pTexture->AddRef();
	}

	void CEntityWater::SetRefractionTexture(CTexture2D* pTexture)
	{
		SAFE_RELEASE(_refractionTexture);
		_refractionTexture = pTexture;
		pTexture->AddRef();
	}

	void CEntityWater::Render()
	{
		if (!_sceneNode || !_bVisible)
			return;

		BOOL ret = FALSE;

		CEngine* engine = GetEngine();
		CCamera* camera = engine->GetCurrentCamera();
		CSceneManager* sceneManager = engine->GetSceneManager();

		CRenderDevice* rd = engine->GetRenderDevice();
		float bumpFactor = (float)timeGetTime() * 0.001f;

		D3DXMATRIX& absTransform = _sceneNode->GetAbsoluteTransformation();

		ret = rd->BindRenderInput(_renderInput);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetMatrix(SCN_MAT_WORLD_VIEW_PROJ, (D3DXMATRIX*)&(absTransform * camera->GetViewProjMatrix()));
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetMatrix(SCN_MAT_WORLD_VIEW, (D3DXMATRIX*)&(absTransform * camera->GetViewMatrix()));
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetMatrix(SCN_MAT_WORLD, (D3DXMATRIX*)&absTransform);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetFloatArray(SCN_EYS_POS, (float*)&camera->GetPosition(), 3);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetTexture(SCN_REFLECTION_TEXTURE, _reflectionTexture);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetTexture(SCN_REFRACTION_TEXTURE, _refractionTexture);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetTexture(SCN_WATER_BUMP_TEXTURE, _bumpTexture);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetFloatArray(SCN_WATER_BUMP_FACTOR, &bumpFactor, 1);
		MK_PROCESS_ERROR(ret);

		DWORD passNum = 0;
		ret = _shader->TechniqueBegin(TECH_WATER_NORMAL, passNum);
		MK_PROCESS_ERROR(ret);

		DWORD vertexCount = (_coreWater->_width + 1) * (_coreWater->_height + 1);
		DWORD faceCount = _coreWater->_width * _coreWater->_height * 2;
		for (DWORD pass = 0; pass < passNum; ++pass)
		{
			if (_shader->PassBegin(pass))
			{
				ret = rd->DrawPrimitive(PT_TRIANGLELIST, 0, 0, vertexCount, 0, faceCount);
				MK_PROCESS_ERROR(ret);

				_shader->PassEnd();
			}
		}

		_shader->TechniqueEnd();

Exit0:
		return;
	}
}